use crate::utils::crypto;
use actix_web::{dev::ServiceRequest, error, web, Error, HttpMessage, Result};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use actix_web_httpauth::middleware::HttpAuthentication;
use serde::{Deserialize, Serialize};
use serde_json::json;

mod account;
pub mod api;
mod app;
pub mod applog;
mod attend;
pub mod device;
pub mod log;
pub mod menu;
pub mod netpoint;
pub mod role;
pub mod user;

fn unauthorized_response(e: &str) -> Error {
    error::ErrorUnauthorized(json!({
        "errorCode": 401,
        "errorMessage": e,
        "success": true,
        "data": {"isLogin": false,},
    }))
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Payload {
    username: String,
    role_id: u64,
    time: i64,
}

async fn validator(
    req: ServiceRequest,
    credentials: BearerAuth,
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
    let payload = crypto::validate_jwt(credentials.token());
    if let Err(e) = payload {
        return Err((unauthorized_response(e.as_str()), req));
    }

    let payload = payload.unwrap();

    let payload = serde_json::from_str::<Payload>(payload.as_str());
    if let Err(e) = payload {
        return Err((unauthorized_response(&e.to_string()), req));
    }

    let payload = payload.unwrap();
    req.extensions_mut().insert(payload.username);

    let role = role::get_role(payload.role_id);
    if role.is_none() {
        return Err((unauthorized_response("角色不存在"), req));
    }
    let role = role.unwrap();
    if role.api_ids.is_none() {
        return Err((unauthorized_response("角色没有API权限"), req));
    }
    let api_ids = role.api_ids.unwrap();

    let api_id = api::get_api_id(req.method().as_ref(), &req.path().to_string());
    if api_id.is_none() {
        return Err((unauthorized_response("API不存在"), req));
    }
    let api_id = api_id.unwrap();

    if !api_ids.contains(&api_id) {
        return Err((
            error::ErrorForbidden(json!({
                "errorMessage": "没有相应权限, 请联系管理员",
                "success": true,
            })),
            req,
        ));
    }

    Ok(req)
}

pub fn api_routes(cfg: &mut web::ServiceConfig) {
    cfg.service(web::scope("/api/account").configure(account::routes))
        .service(
            web::scope("/api")
                .wrap(HttpAuthentication::bearer(validator))
                .configure(user::routes)
                .configure(log::routes)
                .configure(applog::routes)
                .configure(app::routes)
                .configure(role::routes)
                .configure(menu::routes)
                .configure(netpoint::routes)
                .configure(attend::routes)
                .configure(device::routes)
                .configure(api::routes),
        );
}

#[cfg(test)]
mod tests {
    use crate::{
        handlers::{account, api_routes},
        utils::crypto,
        utils::mysql,
    };
    use actix_web::{
        http::header::{self, HeaderMap},
        test, App,
    };
    use serde_json::{json, Value};

    pub async fn get_token() -> String {
        let app = test::init_service(App::new().service(account::login)).await;
        let x = crypto::md5("-@FF#$F#G$^J^-10001-!S!@E!FVEWV-guangbo-123456");
        let now = chrono::Utc::now().timestamp();
        let password = crypto::md5(&(now.to_string() + x.as_str()));
        let req = test::TestRequest::post()
            .uri("/login")
            .set_json(json!({
                "username": "guangbo",
                "password": password,
                "autoLogin": true,
                "type": "account",
                "time": now,
            }))
            .to_request();
        let resp = test::call_service(&app, req).await;
        println!("resp status is {:?}", resp.status());
        assert!(resp.status().is_success());
        let resp_body = test::read_body(resp).await;
        let json_value: Value = serde_json::from_slice(&resp_body).unwrap();
        println!("resp json is {:?}", json_value);
        let status = json_value["status"].as_str().unwrap_or("error");
        assert_eq!(status, "ok");
        let token = json_value["token"].as_str().unwrap();
        println!("resp token is {:?}", token);
        "Bearer ".to_owned() + token
    }

    pub fn insert_authorization_header(headers: &mut HeaderMap, token: String) {
        headers.insert(
            header::AUTHORIZATION,
            header::HeaderValue::from_str(token.as_str()).unwrap(),
        );
    }

    #[actix_web::test]
    async fn test_bearer() {
        mysql::init_pool().await;

        let token = get_token().await;

        let app = test::init_service(App::new().configure(api_routes)).await;

        let mut req = test::TestRequest::get().uri("/api").to_request();

        insert_authorization_header(req.headers_mut(), token);

        let resp = test::call_service(&app, req).await;
        let status = resp.status();
        println!("resp status is {:?}", status);
        let resp_body = test::read_body(resp).await;
        if status.is_server_error() || status.is_client_error() {
            let json_value: Value = serde_json::from_slice(&resp_body).unwrap();
            let error_message = json_value["errorMessage"].as_str().unwrap();
            println!("resp error is {:?}", error_message);
        }

        println!("restp body is {:?}", resp_body);
        assert!(status.is_success());
    }

    #[actix_web::test]
    async fn test_get_current_user() {
        mysql::init_pool().await;

        super::role::init_roles().await.expect("init roles error");
        super::api::init_apis().await.expect("init apis error");

        let token = get_token().await;

        let app = test::init_service(App::new().configure(api_routes)).await;

        let mut req = test::TestRequest::get()
            .uri("/api/user/current")
            .to_request();

        insert_authorization_header(req.headers_mut(), token);

        let resp = test::call_service(&app, req).await;
        let status = resp.status();
        println!("resp status is {:?}", status);
        let resp_body = test::read_body(resp).await;
        println!("restp body is {:?}", resp_body);
        assert!(status.is_success());
    }
}
